package handlers

import (
	"fmt"
	"io"
	"net/http"
	"strconv"
	"strings"
	"time"

	"gitee.com/konglinglong/car/backend/internal/models"
	"github.com/gin-gonic/gin"
)

// Docker Registry API v2 handlers

// RegistryVersion handles the base API version check
func (h *Handlers) RegistryVersion(c *gin.Context) {
	c.Header("Docker-Distribution-API-Version", "registry/2.0")
	c.JSON(http.StatusOK, gin.H{})
}

// RegistryCatalog handles repository catalog listing
func (h *Handlers) RegistryCatalog(c *gin.Context) {
	// Get query parameters
	n := c.DefaultQuery("n", "100")
	last := c.Query("last")

	// Parse limit
	limit := 100
	if parsedN, err := strconv.Atoi(n); err == nil && parsedN > 0 {
		limit = parsedN
		if limit > 1000 {
			limit = 1000 // Cap at 1000
		}
	}

	// Query repositories from database
	var repositories []models.Repository
	query := h.db.Where("repository_type = ?", "docker")

	if last != "" {
		query = query.Where("name > ?", last)
	}

	query = query.Order("name ASC").Limit(limit)

	if err := query.Find(&repositories).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch repositories"})
		return
	}

	// Build repository names list
	repoNames := make([]string, len(repositories))
	for i, repo := range repositories {
		if repo.Namespace != "" {
			repoNames[i] = repo.Namespace + "/" + repo.Name
		} else {
			repoNames[i] = repo.Name
		}
	}

	// Return catalog response
	c.JSON(http.StatusOK, gin.H{
		"repositories": repoNames,
	})
}

// RegistryListTags handles tag listing for a repository
func (h *Handlers) RegistryListTags(c *gin.Context) {
	name := c.Param("name")
	if name == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Repository name is required"})
		return
	}

	// Parse repository name (handle namespace/name format)
	var namespace, repoName string
	parts := strings.Split(name, "/")
	if len(parts) == 2 {
		namespace = parts[0]
		repoName = parts[1]
	} else {
		repoName = name
	}

	// Find repository
	var repository models.Repository
	query := h.db.Where("name = ? AND repository_type = ?", repoName, "docker")
	if namespace != "" {
		query = query.Where("namespace = ?", namespace)
	}

	if err := query.First(&repository).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Repository not found"})
		return
	}

	// Get query parameters
	n := c.DefaultQuery("n", "100")
	last := c.Query("last")

	// Parse limit
	limit := 100
	if parsedN, err := strconv.Atoi(n); err == nil && parsedN > 0 {
		limit = parsedN
		if limit > 1000 {
			limit = 1000
		}
	}

	// Query tags
	var tags []models.Tag
	query = h.db.Where("repository_id = ?", repository.ID)

	if last != "" {
		query = query.Where("name > ?", last)
	}

	query = query.Order("name ASC").Limit(limit)

	if err := query.Find(&tags).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch tags"})
		return
	}

	// Build tag names list
	tagNames := make([]string, len(tags))
	for i, tag := range tags {
		tagNames[i] = tag.Name
	}

	// Return tags response
	c.JSON(http.StatusOK, gin.H{
		"name": name,
		"tags": tagNames,
	})
}

// Manifest operations
func (h *Handlers) RegistryGetManifest(c *gin.Context) {
	name := c.Param("name")
	reference := c.Param("reference")

	if name == "" || reference == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Repository name and reference are required"})
		return
	}

	// Parse repository name
	var namespace, repoName string
	parts := strings.Split(name, "/")
	if len(parts) == 2 {
		namespace = parts[0]
		repoName = parts[1]
	} else {
		repoName = name
	}

	// Find repository
	var repository models.Repository
	query := h.db.Where("name = ? AND repository_type = ?", repoName, "docker")
	if namespace != "" {
		query = query.Where("namespace = ?", namespace)
	}

	if err := query.First(&repository).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Repository not found"})
		return
	}

	var manifest models.Manifest

	// Check if reference is a digest or tag
	if strings.HasPrefix(reference, "sha256:") {
		// Reference is a digest
		if err := h.db.Where("repository_id = ? AND digest = ?", repository.ID, reference).First(&manifest).Error; err != nil {
			c.JSON(http.StatusNotFound, gin.H{"error": "Manifest not found"})
			return
		}
	} else {
		// Reference is a tag
		var tag models.Tag
		if err := h.db.Where("repository_id = ? AND name = ?", repository.ID, reference).First(&tag).Error; err != nil {
			c.JSON(http.StatusNotFound, gin.H{"error": "Tag not found"})
			return
		}

		if tag.ManifestDigest == nil {
			c.JSON(http.StatusNotFound, gin.H{"error": "Manifest not found for tag"})
			return
		}

		if err := h.db.Where("repository_id = ? AND digest = ?", repository.ID, *tag.ManifestDigest).First(&manifest).Error; err != nil {
			c.JSON(http.StatusNotFound, gin.H{"error": "Manifest not found"})
			return
		}
	}

	// Set appropriate headers
	c.Header("Content-Type", manifest.MediaType)
	c.Header("Docker-Content-Digest", manifest.Digest)
	c.Header("Content-Length", strconv.FormatInt(manifest.SizeBytes, 10))

	// Return manifest content
	c.Data(http.StatusOK, manifest.MediaType, manifest.Content)
}

func (h *Handlers) RegistryPutManifest(c *gin.Context) {
	repoName := c.Param("name")
	reference := c.Param("reference")

	// Parse repository name (handle namespaces)
	parts := strings.Split(repoName, "/")
	var namespace, name string
	if len(parts) == 2 {
		namespace = parts[0]
		name = parts[1]
	} else {
		name = repoName
	}

	// Find repository
	var repo models.Repository
	query := h.db.Where("name = ?", name)
	if namespace != "" {
		query = query.Where("namespace = ?", namespace)
	}
	if err := query.First(&repo).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Repository not found"})
		return
	}

	// Read manifest content
	manifestData, err := c.GetRawData()
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to read manifest data"})
		return
	}

	// Get content type
	contentType := c.GetHeader("Content-Type")
	if contentType == "" {
		contentType = "application/vnd.docker.distribution.manifest.v2+json"
	}

	// Create manifest record
	manifest := models.Manifest{
		Digest:       fmt.Sprintf("sha256:%x", manifestData), // Simplified digest calculation
		MediaType:    contentType,
		Content:      manifestData,
		RepositoryID: repo.ID,
		CreatedAt:    time.Now(),
	}

	if err := h.db.Create(&manifest).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create manifest"})
		return
	}

	// If reference is a tag, create or update tag
	if !strings.HasPrefix(reference, "sha256:") {
		var tag models.Tag
		err := h.db.Where("name = ? AND repository_id = ?", reference, repo.ID).First(&tag).Error
		if err != nil {
			// Create new tag
			tag = models.Tag{
				Name:           reference,
				RepositoryID:   repo.ID,
				ManifestDigest: &manifest.Digest,
				CreatedAt:      time.Now(),
			}
			if err := h.db.Create(&tag).Error; err != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create tag"})
				return
			}
		} else {
			// Update existing tag
			tag.ManifestDigest = &manifest.Digest
			tag.UpdatedAt = time.Now()
			if err := h.db.Save(&tag).Error; err != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update tag"})
				return
			}
		}
	}

	// Set response headers
	c.Header("Docker-Content-Digest", manifest.Digest)
	location := fmt.Sprintf("/v2/%s/manifests/%s", repoName, manifest.Digest)
	c.Header("Location", location)

	c.Status(http.StatusCreated)
}

func (h *Handlers) RegistryDeleteManifest(c *gin.Context) {
	repoName := c.Param("name")
	reference := c.Param("reference")

	// Parse repository name (handle namespaces)
	parts := strings.Split(repoName, "/")
	var namespace, name string
	if len(parts) == 2 {
		namespace = parts[0]
		name = parts[1]
	} else {
		name = repoName
	}

	// Find repository
	var repo models.Repository
	query := h.db.Where("name = ?", name)
	if namespace != "" {
		query = query.Where("namespace = ?", namespace)
	}
	if err := query.First(&repo).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Repository not found"})
		return
	}

	// Find and delete manifest
	var manifest models.Manifest
	if strings.HasPrefix(reference, "sha256:") {
		// Delete by digest
		if err := h.db.Where("digest = ? AND repository_id = ?", reference, repo.ID).First(&manifest).Error; err != nil {
			c.JSON(http.StatusNotFound, gin.H{"error": "Manifest not found"})
			return
		}
	} else {
		// Delete by tag
		var tag models.Tag
		if err := h.db.Where("name = ? AND repository_id = ?", reference, repo.ID).First(&tag).Error; err != nil {
			c.JSON(http.StatusNotFound, gin.H{"error": "Tag not found"})
			return
		}
		if tag.ManifestDigest == nil {
			c.JSON(http.StatusNotFound, gin.H{"error": "Manifest not found"})
			return
		}
		if err := h.db.Where("digest = ?", *tag.ManifestDigest).First(&manifest).Error; err != nil {
			c.JSON(http.StatusNotFound, gin.H{"error": "Manifest not found"})
			return
		}
		// Delete the tag
		h.db.Delete(&tag)
	}

	// Delete the manifest
	if err := h.db.Delete(&manifest).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete manifest"})
		return
	}

	c.Status(http.StatusAccepted)
}

// Blob operations
func (h *Handlers) RegistryGetBlob(c *gin.Context) {
	name := c.Param("name")
	digest := c.Param("digest")

	if name == "" || digest == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Repository name and digest are required"})
		return
	}

	// Parse repository name
	var namespace, repoName string
	parts := strings.Split(name, "/")
	if len(parts) == 2 {
		namespace = parts[0]
		repoName = parts[1]
	} else {
		repoName = name
	}

	// Find repository
	var repository models.Repository
	query := h.db.Where("name = ? AND repository_type = ?", repoName, "docker")
	if namespace != "" {
		query = query.Where("namespace = ?", namespace)
	}

	if err := query.First(&repository).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Repository not found"})
		return
	}

	// Find blob
	var blob models.Blob
	if err := h.db.Where("digest = ?", digest).First(&blob).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Blob not found"})
		return
	}

	// Get blob content from storage
	content, err := h.storage.GetObject(c.Request.Context(), blob.StoragePath)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve blob"})
		return
	}
	defer content.Close()

	// Read content into bytes
	blobData, err := io.ReadAll(content)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read blob content"})
		return
	}

	// Set appropriate headers
	c.Header("Content-Type", "application/octet-stream")
	c.Header("Docker-Content-Digest", digest)
	c.Header("Content-Length", fmt.Sprintf("%d", blob.SizeBytes))

	// Return blob content
	c.Data(http.StatusOK, "application/octet-stream", blobData)
}

func (h *Handlers) RegistryHeadBlob(c *gin.Context) {
	name := c.Param("name")
	digest := c.Param("digest")

	if name == "" || digest == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Repository name and digest are required"})
		return
	}

	// Parse repository name
	var namespace, repoName string
	parts := strings.Split(name, "/")
	if len(parts) == 2 {
		namespace = parts[0]
		repoName = parts[1]
	} else {
		repoName = name
	}

	// Find repository
	var repository models.Repository
	query := h.db.Where("name = ? AND repository_type = ?", repoName, "docker")
	if namespace != "" {
		query = query.Where("namespace = ?", namespace)
	}

	if err := query.First(&repository).Error; err != nil {
		c.Status(http.StatusNotFound)
		return
	}

	// Find blob
	var blob models.Blob
	if err := h.db.Where("digest = ?", digest).First(&blob).Error; err != nil {
		c.Status(http.StatusNotFound)
		return
	}

	// Set appropriate headers
	c.Header("Content-Type", "application/octet-stream")
	c.Header("Docker-Content-Digest", blob.Digest)
	c.Header("Content-Length", strconv.FormatInt(blob.SizeBytes, 10))

	// Return 200 OK with headers only
	c.Status(http.StatusOK)
}

func (h *Handlers) RegistryDeleteBlob(c *gin.Context) {
	repoName := c.Param("name")
	digest := c.Param("digest")

	// Parse repository name (handle namespaces)
	parts := strings.Split(repoName, "/")
	var namespace, name string
	if len(parts) == 2 {
		namespace = parts[0]
		name = parts[1]
	} else {
		name = repoName
	}

	// Find repository
	var repo models.Repository
	query := h.db.Where("name = ?", name)
	if namespace != "" {
		query = query.Where("namespace = ?", namespace)
	}
	if err := query.First(&repo).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Repository not found"})
		return
	}

	// Find and delete blob
	var blob models.Blob
	if err := h.db.Where("digest = ?", digest).First(&blob).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Blob not found"})
		return
	}

	// TODO: Delete blob from storage
	// For now, just delete from database
	if err := h.db.Delete(&blob).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete blob"})
		return
	}

	c.Status(http.StatusAccepted)
}

// Blob upload operations
func (h *Handlers) RegistryInitiateUpload(c *gin.Context) {
	name := c.Param("name")
	if name == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Repository name is required"})
		return
	}

	// Parse repository name
	var namespace, repoName string
	parts := strings.Split(name, "/")
	if len(parts) == 2 {
		namespace = parts[0]
		repoName = parts[1]
	} else {
		repoName = name
	}

	// Find repository
	var repository models.Repository
	query := h.db.Where("name = ? AND repository_type = ?", repoName, "docker")
	if namespace != "" {
		query = query.Where("namespace = ?", namespace)
	}

	if err := query.First(&repository).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Repository not found"})
		return
	}

	// Create upload session
	upload := models.Upload{
		UUID:         generateUploadUUID(),
		RepositoryID: repository.ID,
		SizeBytes:    0,
		StoragePath:  generateUploadPath(),
		Status:       "active",
		StartedAt:    time.Now(),
		ExpiresAt:    time.Now().Add(24 * time.Hour), // 24 hour expiry
	}

	if err := h.db.Create(&upload).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create upload session"})
		return
	}

	// Set location header for upload URL
	location := fmt.Sprintf("/v2/%s/blobs/uploads/%s", name, upload.UUID)
	c.Header("Location", location)
	c.Header("Docker-Upload-UUID", upload.UUID)
	c.Header("Range", "0-0")

	c.Status(http.StatusAccepted)
}

func (h *Handlers) RegistryGetUploadStatus(c *gin.Context) {
	name := c.Param("name")
	uuid := c.Param("uuid")

	if name == "" || uuid == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Repository name and upload UUID are required"})
		return
	}

	// Find upload session
	var upload models.Upload
	if err := h.db.Where("uuid = ? AND status = ?", uuid, "active").First(&upload).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Upload session not found"})
		return
	}

	// Check if upload has expired
	if time.Now().After(upload.ExpiresAt) {
		// Mark as cancelled and return error
		upload.Status = "cancelled"
		h.db.Save(&upload)
		c.JSON(http.StatusNotFound, gin.H{"error": "Upload session expired"})
		return
	}

	// Set headers
	c.Header("Docker-Upload-UUID", upload.UUID)
	c.Header("Range", fmt.Sprintf("0-%d", upload.SizeBytes))

	// Return upload status
	c.Status(http.StatusNoContent)
}

func (h *Handlers) RegistryUploadChunk(c *gin.Context) {
	repoName := c.Param("name")
	uploadUUID := c.Param("uuid")

	// Parse repository name (handle namespaces)
	parts := strings.Split(repoName, "/")
	var namespace, name string
	if len(parts) == 2 {
		namespace = parts[0]
		name = parts[1]
	} else {
		name = repoName
	}

	// Find repository
	var repo models.Repository
	query := h.db.Where("name = ?", name)
	if namespace != "" {
		query = query.Where("namespace = ?", namespace)
	}
	if err := query.First(&repo).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Repository not found"})
		return
	}

	// Find upload session
	var upload models.Upload
	if err := h.db.Where("uuid = ? AND repository_id = ? AND status = ?", uploadUUID, repo.ID, "active").First(&upload).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Upload not found"})
		return
	}

	// Check if upload has expired
	if time.Now().After(upload.ExpiresAt) {
		c.JSON(http.StatusNotFound, gin.H{"error": "Upload expired"})
		return
	}

	// Read chunk data
	chunkData, err := c.GetRawData()
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to read chunk data"})
		return
	}

	// TODO: Append chunk to storage
	// For now, just update the upload size
	upload.SizeBytes += int64(len(chunkData))

	if err := h.db.Save(&upload).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update upload"})
		return
	}

	// Set response headers
	c.Header("Docker-Upload-UUID", uploadUUID)
	c.Header("Range", fmt.Sprintf("0-%d", upload.SizeBytes-1))
	c.Status(http.StatusAccepted)
}

func (h *Handlers) RegistryCompleteUpload(c *gin.Context) {
	repoName := c.Param("name")
	uploadUUID := c.Param("uuid")
	digest := c.Query("digest")

	if digest == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Digest parameter is required"})
		return
	}

	// Parse repository name (handle namespaces)
	parts := strings.Split(repoName, "/")
	var namespace, name string
	if len(parts) == 2 {
		namespace = parts[0]
		name = parts[1]
	} else {
		name = repoName
	}

	// Find repository
	var repo models.Repository
	query := h.db.Where("name = ?", name)
	if namespace != "" {
		query = query.Where("namespace = ?", namespace)
	}
	if err := query.First(&repo).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Repository not found"})
		return
	}

	// Find upload session
	var upload models.Upload
	if err := h.db.Where("uuid = ? AND repository_id = ? AND status = ?", uploadUUID, repo.ID, "active").First(&upload).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Upload not found"})
		return
	}

	// Check if upload has expired
	if time.Now().After(upload.ExpiresAt) {
		c.JSON(http.StatusNotFound, gin.H{"error": "Upload expired"})
		return
	}

	// Create blob record
	blob := models.Blob{
		Digest:      digest,
		SizeBytes:   upload.SizeBytes,
		StoragePath: upload.StoragePath,
		CreatedAt:   time.Now(),
	}

	if err := h.db.Create(&blob).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create blob"})
		return
	}

	// Mark upload as completed
	upload.Status = "completed"
	if err := h.db.Save(&upload).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to complete upload"})
		return
	}

	// Set response headers
	c.Header("Docker-Content-Digest", digest)
	location := fmt.Sprintf("/v2/%s/blobs/%s", repoName, digest)
	c.Header("Location", location)

	c.Status(http.StatusCreated)
}

func (h *Handlers) RegistryCancelUpload(c *gin.Context) {
	repoName := c.Param("name")
	uploadUUID := c.Param("uuid")

	// Parse repository name (handle namespaces)
	parts := strings.Split(repoName, "/")
	var namespace, name string
	if len(parts) == 2 {
		namespace = parts[0]
		name = parts[1]
	} else {
		name = repoName
	}

	// Find repository
	var repo models.Repository
	query := h.db.Where("name = ?", name)
	if namespace != "" {
		query = query.Where("namespace = ?", namespace)
	}
	if err := query.First(&repo).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Repository not found"})
		return
	}

	// Find and cancel upload session
	var upload models.Upload
	if err := h.db.Where("uuid = ? AND repository_id = ?", uploadUUID, repo.ID).First(&upload).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Upload not found"})
		return
	}

	// Mark upload as cancelled
	upload.Status = "cancelled"
	if err := h.db.Save(&upload).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to cancel upload"})
		return
	}

	// TODO: Clean up temporary storage

	c.Status(http.StatusNoContent)
}

// Helper functions
func generateUploadUUID() string {
	// Generate a simple UUID-like string for upload sessions
	return fmt.Sprintf("%d-%d", time.Now().UnixNano(), time.Now().Unix())
}

func generateUploadPath() string {
	// Generate a temporary storage path for uploads
	return fmt.Sprintf("uploads/temp_%d", time.Now().UnixNano())
}
